#include "PhysicalDisk.h"
#include "SuperBlock.h"
#include "FreeAddressesBlock.h"
#include <stdio.h>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
#include <fstream>


using namespace std;

PhysicalDisk::PhysicalDisk() {
	fd = open("FILE_SYS", O_RDWR, 0666);
	superBlock = new SuperBlock(fd);
}

PhysicalDisk::PhysicalDisk(int diskSizeInBytes, int dataBlockSize, int numberOfInodes) {

	fd = open("FILE_SYS", O_CREAT | O_RDWR, 0666);
	int sizeOfChar = sizeof(char);

	//formats the disk with 0s
	char zero = 0;
	for (int i=0; i<diskSizeInBytes; i++) {
		write(fd,&zero,sizeOfChar);
	}

	superBlock = new SuperBlock(diskSizeInBytes, dataBlockSize, numberOfInodes, fd);

	//calculates the number of future blocks that would be occupied containing addresses of free blocks
	int numOfAddressesInBlock = (dataBlockSize-4)/4;

	/* fills up special blocks with free blocks addresses */
	vector<FreeAddressesBlock*> freeAdrBlocks = allocateBlocksForFreeBlocksAddresses(numOfAddressesInBlock, dataBlockSize);
	superBlock->setNumOfFreeBlocksInFileSys(superBlock->getNumOfFreeBlocksInFileSys() - freeAdrBlocks.size() +1 );
	superBlock->writeToPhysicalDisk();
	writeBlocksOfFreeAddresses(freeAdrBlocks);

	/* fills up special blocks with free inodes addresses */
	vector<FreeAddressesBlock*> freeAdrInodes = allocateBlocksForFreeInodesAddresses (numOfAddressesInBlock, dataBlockSize, numberOfInodes);
	superBlock->writeToPhysicalDisk();
	writeBlocksOfFreeAddresses(freeAdrInodes);
}

PhysicalDisk::~PhysicalDisk(){
	delete (superBlock);
	close(fd);
}

int PhysicalDisk::getFileDesc() {
	return fd;
}

int PhysicalDisk::getBlockSize() {
	return superBlock->getSizeOfBlock();
}

int PhysicalDisk::getNumOfInodes() {
	return superBlock->getSizeOfINodeArray();
}

vector<int> PhysicalDisk::calcFreeAddressesBlocks(int numOfAddresses) {
	vector<int> addresses;
	int startOffset = superBlock->getSizeOfBlock()*(3 + superBlock->getNumOfOccupiedBlocksByInodes());
	addresses.push_back(superBlock->getPointerIntoBlockOfFreeBlocks());

	for (int i=0; i<numOfAddresses; i++) {
		addresses.push_back(startOffset);
		startOffset+=superBlock->getSizeOfBlock();
	}
	return addresses;
}

vector<int> PhysicalDisk::calcFreeINodesBlocks(int numOfAddresses, int dataBlockSize) {
	vector<int> addresses;
	addresses.push_back(superBlock->getPointerIntoBlockOfFreeINodes());
/*	FreeAddressesBlock currBlock = FreeAddressesBlock (1*dataBlockSize,dataBlockSize, fd);
	currBlock.loadFromPhysicalDisk();*/
	int freeAddress;
	for (int i=0; i<numOfAddresses; i++) {
		//freeAddress = currBlock.getNextFreeAddress();
		//calculates the free block's address
		freeAddress = allocateBlock();
		addresses.push_back(freeAddress);
	}
	return addresses;
}

void PhysicalDisk::writeBlocksOfFreeAddresses(vector<FreeAddressesBlock*>& addressBlocks) {
	for (int i=0; i<(int)addressBlocks.size(); i++) {
		FreeAddressesBlock* currAddressBlock = addressBlocks.at(i);
		currAddressBlock->writeToPhysicalDisk();
	}
}

FreeAddressesBlock* PhysicalDisk::makeObjectAddressesBlock(vector<int> & freeAddressesBlocks,
		int & dataBlockSize, int & freeInodesCounter, int & freeBlocksOffset, int objectSize) {
	FreeAddressesBlock *currBlock = new FreeAddressesBlock(freeAddressesBlocks.front(), dataBlockSize, fd);
    while(freeInodesCounter > 0 && currBlock->hasMoreSpace()){
        currBlock->addFreeAddress(freeBlocksOffset);
        freeBlocksOffset += objectSize;
        freeInodesCounter--;
    }
    if(freeInodesCounter > 0){
        freeAddressesBlocks.erase(freeAddressesBlocks.begin());
        currBlock->setNextFreeAddressesBlock(freeAddressesBlocks.front());
    }
    return currBlock;
}

vector<FreeAddressesBlock*> PhysicalDisk::allocateBlocksForFreeBlocksAddresses (int numOfAddressesInBlock, int dataBlockSize) {
	int numOfFreeBlocks = superBlock->getNumOfFreeBlocksInFileSys();

	int numOfFreeAddressBlocks = numOfFreeBlocks % numOfAddressesInBlock == 0
			? numOfFreeBlocks/numOfAddressesInBlock
					: (numOfFreeBlocks/numOfAddressesInBlock)+1;
	//removes the default block (1)
	numOfFreeAddressBlocks--;

	vector<int> freeAddressesBlocks = calcFreeAddressesBlocks(numOfFreeAddressBlocks);

	//sets the address of the last block containing a list of free blocks
	superBlock->setPointerToTheLastBlockOfFreeBlocks(freeAddressesBlocks.back());

	int freeBlocksOffset = dataBlockSize*(3 + superBlock->getNumOfOccupiedBlocksByInodes() + numOfFreeAddressBlocks);

	int freeBlocksCounter = numOfFreeBlocks - numOfFreeAddressBlocks;

	vector<FreeAddressesBlock*> freeAdrBlocks;

	while (freeBlocksCounter>0) {
		freeAdrBlocks.push_back(makeObjectAddressesBlock(freeAddressesBlocks, dataBlockSize,
						freeBlocksCounter, freeBlocksOffset, dataBlockSize));
	}

	return freeAdrBlocks;
}

vector<FreeAddressesBlock*> PhysicalDisk::allocateBlocksForFreeInodesAddresses (int numOfAddressesInBlock, int dataBlockSize, int numberOfInodes) {
	int numOfBlocksForINodes = numberOfInodes % numOfAddressesInBlock == 0
			? numberOfInodes/numOfAddressesInBlock
					: (numberOfInodes/numOfAddressesInBlock)+1;
	//removes the default block (2)
	numOfBlocksForINodes--;

	vector<int> freeInodesAddressesBlocks = calcFreeINodesBlocks(numOfBlocksForINodes, dataBlockSize);

	//sets the address of the last block containing a list of inode blocks
	superBlock->setPointerToTheLastBlockOfFreeINodes(freeInodesAddressesBlocks.back());

	int iNodesBlocksOffset = dataBlockSize*3;

	vector<FreeAddressesBlock*> freeAdrInodes;
	int inodesAddressesAlocatorCounter = numberOfInodes;
	while (inodesAddressesAlocatorCounter>0) {
		freeAdrInodes.push_back(makeObjectAddressesBlock(freeInodesAddressesBlocks, dataBlockSize,
				inodesAddressesAlocatorCounter, iNodesBlocksOffset, 50));
	}
	return freeAdrInodes;
}

int PhysicalDisk::allocateBlock() {
	int numOfAllocatedBlock;
	int pointerToOffsetOfNewBlock = superBlock->getPointerIntoBlockOfFreeBlocks();
	if (superBlock->getNumOfFreeBlocksInFileSys()==0) { //there are no free blocks in the system (-1 will be returned)
		return -1;
	}
	//there are no more free blocks addresses to allocate in the block
	if (2*superBlock->getSizeOfBlock()-pointerToOffsetOfNewBlock < 8) {
		//loads the first block of free addresses into memory
		int nextFreeAdrBlockAddress;
		DiskUtils::readFromDisk(&nextFreeAdrBlockAddress,pointerToOffsetOfNewBlock,sizeof(int));
		if (nextFreeAdrBlockAddress!=0) {
			//searches a free block address in the next block in the list
			FreeAddressesBlock nextFreeAdrBlock(nextFreeAdrBlockAddress,superBlock->getSizeOfBlock(),fd);
			nextFreeAdrBlock.loadFromPhysicalDisk();

			//nextFreeAdrBlock.printAddresses();
			nextFreeAdrBlock.setOffset(superBlock->getSizeOfBlock());
			nextFreeAdrBlock.writeToPhysicalDisk();
			//deletes the old free addresses block
			if (nextFreeAdrBlockAddress == superBlock->getPointerToTheLastBlockOfFreeBlocks()) {
				superBlock->setPointerToTheLastBlockOfFreeBlocks(superBlock->getSizeOfBlock());
				superBlock->writeToPhysicalDisk();
			}
			freeBlock(nextFreeAdrBlockAddress);
			//sets the pointer to the begining of block 1
			superBlock->setPointerIntoBlockOfFreeBlocks(superBlock->getSizeOfBlock());
			superBlock->writeToPhysicalDisk();

		} /*else {
			return -1;//there are no free blocks in the system (-1 will be returned)
		}*/
	}
	int freeBlocksAddress;
	DiskUtils::readFromDisk(&freeBlocksAddress,superBlock->getPointerIntoBlockOfFreeBlocks(),sizeof(int));
	numOfAllocatedBlock = freeBlocksAddress;
	//delete the address from disk
	int zero = 0;
	DiskUtils::writeToDisk(&zero,superBlock->getPointerIntoBlockOfFreeBlocks(),sizeof(int));
	superBlock->decNumOfFreeBlocksInFileSys();
	//there are no more empty blocks
	if (superBlock->getNumOfFreeBlocksInFileSys() == 0) {
		//point to the next address in the free addresses block
		superBlock->setPointerIntoBlockOfFreeBlocks(superBlock->getSizeOfBlock());
	} else {
		//point to the next address in the free addresses block
		superBlock->setPointerIntoBlockOfFreeBlocks(superBlock->getPointerIntoBlockOfFreeBlocks() + sizeof(int));
	}
	superBlock->writeToPhysicalDisk();
	return numOfAllocatedBlock;
}

void PhysicalDisk::freeBlock(int blockOffset) {
	FreeAddressesBlock lastBlockOfFreeAdr(superBlock->getPointerToTheLastBlockOfFreeBlocks(),superBlock->getSizeOfBlock(),fd);
	lastBlockOfFreeAdr.loadFromPhysicalDisk();
	//there is no place for the new address of free block
	if (lastBlockOfFreeAdr.addFreeAddress(blockOffset) == -1) {
		int newAddressForAddressesBlock = allocateBlock();
		lastBlockOfFreeAdr.setNextFreeAddressesBlock(newAddressForAddressesBlock);
		FreeAddressesBlock newBlockOfFreeAdr(newAddressForAddressesBlock,superBlock->getSizeOfBlock(),fd);
		newBlockOfFreeAdr.addFreeAddress(blockOffset);
		newBlockOfFreeAdr.writeToPhysicalDisk();
		superBlock->setPointerToTheLastBlockOfFreeBlocks(newAddressForAddressesBlock);
		superBlock->writeToPhysicalDisk();
	}
	DiskUtils::eraseBlock(blockOffset,superBlock->getSizeOfBlock());
	lastBlockOfFreeAdr.writeToPhysicalDisk();
	superBlock->incNumOfFreeBlocksInFileSys();
	superBlock->writeToPhysicalDisk();
}

int PhysicalDisk::allocateInode() {
	int numOfAllocatedInode;
	int pointerToOffsetOfNewInode = superBlock->getPointerIntoBlockOfFreeINodes();
	if (superBlock->getNumOfFreeINodesInFileSys()==0) { //there are no free blocks in the system (-1 will be returned)
		return -1;
	}
	//there are no more free inodes addresses to allocate in the block
	if (3*superBlock->getSizeOfBlock()-pointerToOffsetOfNewInode < 8) {
		//loads the first block of free addresses into memory
		int nextFreeAdrBlockAddress;
		DiskUtils::readFromDisk(&nextFreeAdrBlockAddress,pointerToOffsetOfNewInode,sizeof(int));
		if (nextFreeAdrBlockAddress!=0) {
			//searches a free block address in the next block in the list
			FreeAddressesBlock nextFreeAdrBlock(nextFreeAdrBlockAddress,superBlock->getSizeOfBlock(),fd);
			nextFreeAdrBlock.loadFromPhysicalDisk();

			//nextFreeAdrBlock.printAddresses();
			nextFreeAdrBlock.setOffset(superBlock->getSizeOfBlock()*2);
			nextFreeAdrBlock.writeToPhysicalDisk();
			//deletes the old free addresses block
			if (nextFreeAdrBlockAddress == superBlock->getPointerToTheLastBlockOfFreeINodes()) {
				superBlock->setPointerToTheLastBlockOfFreeINodes(superBlock->getSizeOfBlock()*2);
				superBlock->writeToPhysicalDisk();
			}
			freeBlock(nextFreeAdrBlockAddress);
			//sets the pointer to the begining of block 1
			superBlock->setPointerIntoBlockOfFreeINodes(superBlock->getSizeOfBlock()*2);
			superBlock->writeToPhysicalDisk();

		} /*else {
			return -1;//there are no free blocks in the system (-1 will be returned)
		}*/
	}
	int freeBlocksAddress;
	DiskUtils::readFromDisk(&freeBlocksAddress,superBlock->getPointerIntoBlockOfFreeINodes(),sizeof(int));
	numOfAllocatedInode = freeBlocksAddress;
	//delete the address from disk
	int zero = 0;
	DiskUtils::writeToDisk(&zero,superBlock->getPointerIntoBlockOfFreeINodes(),sizeof(int));
	superBlock->decNumOfFreeInodesInFileSys();
	//there are no more empty blocks
	if (superBlock->getNumOfFreeINodesInFileSys() == 0) {
		//point to the next address in the free addresses block
		superBlock->setPointerIntoBlockOfFreeINodes(superBlock->getSizeOfBlock()*2);
	} else {
		//point to the next address in the free addresses block
		superBlock->setPointerIntoBlockOfFreeINodes(superBlock->getPointerIntoBlockOfFreeINodes() + sizeof(int));
	}
	superBlock->writeToPhysicalDisk();
	return numOfAllocatedInode;

}

void PhysicalDisk::freeInode(int inodeOffset) {
	FreeAddressesBlock lastBlockOfFreeAdr(superBlock->getPointerToTheLastBlockOfFreeINodes(),superBlock->getSizeOfBlock(),fd);
	lastBlockOfFreeAdr.loadFromPhysicalDisk();
	//there is no place for the new address of free inode
	if (lastBlockOfFreeAdr.addFreeAddress(inodeOffset) == -1) {
		int newAddressForAddressesBlock = allocateBlock();
		lastBlockOfFreeAdr.setNextFreeAddressesBlock(newAddressForAddressesBlock);
		FreeAddressesBlock newBlockOfFreeAdr(newAddressForAddressesBlock,superBlock->getSizeOfBlock(),fd);
		newBlockOfFreeAdr.addFreeAddress(inodeOffset);
		newBlockOfFreeAdr.writeToPhysicalDisk();
		superBlock->setPointerToTheLastBlockOfFreeINodes(newAddressForAddressesBlock);
		superBlock->writeToPhysicalDisk();
	}
	DiskUtils::eraseBlock(inodeOffset,50);
	lastBlockOfFreeAdr.writeToPhysicalDisk();
	superBlock->incNumOfFreeInodesInFileSys();
	superBlock->writeToPhysicalDisk();
}




void PhysicalDisk::main() {
	for (int i=0; i<51; i++) {
		cout << "Allocated inode number: " << allocateInode() << endl;
	}
	freeInode(192);
	freeInode(242);
	freeInode(292);
	freeInode(342);
	freeInode(392);
	freeInode(442);
	freeInode(492);
	freeInode(542);
	freeInode(592);
	freeInode(642);
	freeInode(692);
	freeInode(742);
	freeInode(792);
	freeInode(842);
	freeInode(892);
	freeInode(942);


	/*for (int i=0; i<20; i++) {
		cout << "Allocated block number: " << allocateBlock() << endl;
	}
	freeBlock(3008);
	freeBlock(3072);
	freeBlock(3136);
	freeBlock(3200);
	freeBlock(3264);
	freeBlock(3328);
	freeBlock(3392);
	freeBlock(3456);
	freeBlock(3520);
	freeBlock(3584);
	freeBlock(3648);
	freeBlock(3712);
	freeBlock(3776);
	freeBlock(3840);
	freeBlock(3904);
	freeBlock(3968);
	freeBlock(4032);
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	allocateBlock();
	cout <<	allocateBlock() << endl;
	cout << allocateBlock()<< endl;
	cout << allocateBlock()<< endl;

	cout << allocateBlock()<< endl;*/



	/*FreeAddressesBlock b(0,64,fd);
	while (b.hasMoreSpace()) {
		b.addFreeAddress(1);
	}
	b.writeToPhysicalDisk();*/
	/*FreeAddressesBlock b(64,64,fd);
	b.loadFromPhysicalDisk();
	b.printAddresses();*/
}
